package com.visionblue.business.ship.modbus;

import android.util.Log;

import com.deemons.serialportlib.SerialPort;
import com.visionblue.business.ship.modbus.exception.ModbusError;
import com.visionblue.business.ship.modbus.exception.ModbusErrorType;
import com.visionblue.business.ship.modbus.utilities.ByteArrayReader;
import com.visionblue.business.ship.modbus.utilities.ByteArrayWriter;
import com.visionblue.business.ship.modbus.utilities.CRC16;
import com.visionblue.business.ship.modbus.utilities.ThreadUtil;
import com.visionblue.business.ship.modbus.utilities.TimeoutUtil;
import com.visionblue.business.ship.util.MLog;

import java.io.IOException;
import java.util.Arrays;
import java.util.BitSet;


/*
    Modbus RTU master
 */
public class ModBusMaster {
    private int timeout = 1000;
    private SerialPort port;

    public ModBusMaster(SerialPort port) {
        this.port = port;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    // slave - 设备地址，functionCode - 操作命令码 ，startingAddress - 起始寄存器地址，quantityOfX - 寄存器长度
    synchronized public int[] execute(int slave, int functionCode, int startingAddress, int quantityOfX, int outputValue) throws IOException, ModbusError {
        Log.e("流量计-未处理原始指令", "设备地址：" + slave + "功能码：" + functionCode + "起始寄存器地址:" + startingAddress + "寄存器长度：" + quantityOfX);

        // 串口测试假数据时暂时关闭
        if (port.getOutputStream() == null) {
            return new int[quantityOfX];
        }

        if (slave < 0 || slave > 255) {
            throw new ModbusError(ModbusErrorType.ModbusInvalidArgumentError, "Invalid slave " + slave);
        }
        if (startingAddress < 0 || startingAddress > 0xffff) {
            throw new ModbusError(ModbusErrorType.ModbusInvalidArgumentError, "Invalid startingAddress " + startingAddress);
        }
        if (quantityOfX < 1 || quantityOfX > 0xff) {
            throw new ModbusError(ModbusErrorType.ModbusInvalidArgumentError, "Invalid quantityOfX " + quantityOfX);
        }

        boolean isReadFunction = false;
        int expectedLength = 0;
        // 构造request
        ByteArrayWriter request = new ByteArrayWriter();
        request.writeInt8(slave);
        if (functionCode == ModbusFunction.READ_COILS || functionCode == ModbusFunction.READ_DISCRETE_INPUTS) {
            isReadFunction = true;
            request.writeInt8(functionCode);
            request.writeInt16(startingAddress);
            request.writeInt16(quantityOfX);

            expectedLength = (int) Math.ceil(0.1d * quantityOfX / 8.0) + 5;
        } else if (functionCode == ModbusFunction.READ_INPUT_REGISTERS || functionCode == ModbusFunction.READ_HOLDING_REGISTERS) {
            isReadFunction = true;
            request.writeInt8(functionCode); // 功能码
            request.writeInt16(startingAddress); // 起始寄存器地址
            request.writeInt16(quantityOfX); // 读取寄存器个数

            expectedLength = 2 * quantityOfX + 5;
        } else if (functionCode == ModbusFunction.WRITE_SINGLE_COIL || functionCode == ModbusFunction.WRITE_SINGLE_REGISTER) {
            if (functionCode == ModbusFunction.WRITE_SINGLE_COIL)
                if (outputValue != 0) outputValue = 0xff00;
            request.writeInt8(functionCode);
            request.writeInt16(startingAddress);
            request.writeInt16(outputValue);

            expectedLength = 8;
        } else {
            throw new ModbusError(ModbusErrorType.ModbusFunctionNotSupportedError, "Not support function " + functionCode);
        }

        byte[] bytes = request.toByteArray();
        int crc = CRC16.compute(bytes);
        request.writeInt16Reversal(crc);
        // 发送到设备
        bytes = request.toByteArray();
        MLog.e("bytes = " + Arrays.toString(bytes));
        //Log.e("流量计-发送给设备指令1：",bytes.toString());
        Log.e("流量计-发给设备指令：", Arrays.toString(bytes));
        port.getOutputStream().write(bytes);
        // 从设备接收反馈
        byte[] responseBytes;
        try (ByteArrayWriter response = new ByteArrayWriter()) {
            ThreadUtil.sleep(150);
            final int finalExpectedLength = expectedLength;
            final boolean[] complete = new boolean[1];
            boolean done = TimeoutUtil.execute(() -> {
                try {
                    byte[] bytes1 = new byte[64];
                    while (!complete[0]) {
                        if (port.getInputStream().available() > 0) {
                            int len = port.getInputStream().read(bytes1, 0, bytes1.length);
                            if (len > 0) {
                                response.write(bytes1, 0, len);
                                if (response.size() >= finalExpectedLength) {
                                    break;
                                }
                            }
                        }
                        ThreadUtil.sleep(1);
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }, timeout);
            complete[0] = true;
            response.flush();

            if (!done) {
                throw new ModbusError(ModbusErrorType.ModbusTimeoutError, String.format("Timeout of %d ms.", timeout));
            }
            responseBytes = response.toByteArray();
        }

        if (responseBytes == null || responseBytes.length != expectedLength) {
            throw new ModbusError(ModbusErrorType.ModbusInvalidResponseError, "Response length is invalid " + (responseBytes == null ? 0 : responseBytes.length));
        }

//        ByteArrayWriter response = new ByteArrayWriter();
//                16 04 2C 41 AE
//        response.write(22);
//        response.write(4);
//        response.write(44);
//
//        response.write(65);
//        response.write(174);
//        00 00 3F E9 37
//        response.write(0);
//        response.write(0);
//
//        response.write(63);
//        response.write(233);
//        response.write(57);
//        4C 42 11 00 00
//        response.write(76);
//        response.write(66);
//        response.write(17);
//        response.write(0);
//        response.write(0);
        //        3F 80 00 00 00
//        response.write(63);
//        response.write(128);
//        response.write(0);
//        response.write(0);
//        response.write(0);
        //        00 00 01 3E ED
//        response.write(0);
//        response.write(0);
//        response.write(1);
//        response.write(62);
//        response.write(237);
        //        0E 56 00 00 00
//        response.write(14);
//        response.write(86);
//        response.write(0);
//        response.write(0);
//        response.write(0);
        //        00 3C 03 12 6F
//        response.write(0);
//        response.write(60);
//        response.write(3);
//        response.write(18);
//        response.write(111);
        //        00 05 00 01 00
//        response.write(0);
//        response.write(5);
//        response.write(0);
//        response.write(1);
//        response.write(0);
        //        00 00 00 00 00
//        response.write(0);
//        response.write(0);
//        response.write(0);
//        response.write(0);
//        response.write(0);
        //        00 00 6C 53
//        response.write(0);
//        response.write(0);
//        response.write(108);
//        response.write(83);

//        responseBytes = response.toByteArray();
        ByteArrayReader reader = new ByteArrayReader(responseBytes);
        int responseSlave = reader.readInt8();
        if (responseSlave != slave) {
            throw new ModbusError(ModbusErrorType.ModbusInvalidResponseError,
                    String.format("Response slave %d is different from request slave %d", responseSlave, slave));
        }

        int return_code = reader.readInt8();
        if (return_code > 0x80) {
            int error_code = reader.readInt8();
            throw new ModbusError(error_code);
        }

        int dataLength = 0;
        if (isReadFunction) {
            // get the values returned by the reading function
            dataLength = reader.readInt8();
            int actualLength = responseBytes.length - 5;
            if (dataLength != actualLength) {
                throw new ModbusError(ModbusErrorType.ModbusInvalidResponseError,
                        String.format("Byte count is %d while actual number of bytes is %d. ", dataLength, actualLength));
            }
        }
        // 读取反馈数据
        int[] result = new int[quantityOfX];
        if (functionCode == ModbusFunction.READ_COILS || functionCode == ModbusFunction.READ_DISCRETE_INPUTS) {
            bytes = new byte[dataLength];
            for (int i = 0; i < dataLength; i++) {
                bytes[i] = (byte) reader.readInt8();
            }
            BitSet bits = BitSet.valueOf(bytes);
            for (int i = 0; i < quantityOfX; i++) {
                result[i] = bits.get(i) ? 1 : 0;
            }
        } else if ( functionCode == ModbusFunction.READ_HOLDING_REGISTERS || functionCode == ModbusFunction.READ_INPUT_REGISTERS) {
            for (int i = 0; i < quantityOfX; i++) {
                result[i] = reader.readInt16();
            }
        } else if (functionCode == ModbusFunction.WRITE_SINGLE_COIL || functionCode == ModbusFunction.WRITE_SINGLE_REGISTER) {
            result[0] = reader.readInt16();
            //result[1] = reader.readInt16();
        }
        return result;
    }

    public int[] readCoils(int slave, int startAddress, int numberOfPoints) throws IOException, ModbusError {
        return execute(slave, ModbusFunction.READ_COILS, startAddress, numberOfPoints, 0);
    }

    // slave - 设备地址，startAddress - 起始寄存器地址，numberOfPoints - 寄存器长度
    public int[] readHoldingRegisters(int slave, int startAddress, int numberOfPoints) throws IOException, ModbusError {
        return execute(slave, ModbusFunction.READ_HOLDING_REGISTERS, startAddress, numberOfPoints, 0);
    }

    public int[] readInputRegisters(int slave, int startAddress, int numberOfPoints) throws IOException, ModbusError {
        return execute(slave, ModbusFunction.READ_INPUT_REGISTERS, startAddress, numberOfPoints, 0);
    }

    public int[] readInputs(int slave, int startAddress, int numberOfPoints) throws IOException, ModbusError {
        return execute(slave, ModbusFunction.READ_DISCRETE_INPUTS, startAddress, numberOfPoints, 0);
    }

    public void writeSingleCoil(int slave, int address, boolean value) throws IOException, ModbusError {
        execute(slave, ModbusFunction.WRITE_SINGLE_COIL, address, 1, value ? 1 : 0);
    }

    public void writeSingleRegister(int slave, int address, int value) throws IOException, ModbusError {
        execute(slave, ModbusFunction.WRITE_SINGLE_REGISTER, address, 1, value);
    }

    public boolean readCoil(int slave, int address) throws IOException, ModbusError {
        int[] values = readCoils(slave, address, 1);
        return values[0] > 0;
    }

    public int readHoldingRegister(int slave, int address) throws IOException, ModbusError {
        int[] values = readHoldingRegisters(slave, address, 1);
        return values[0];
    }

    public int readInputRegister(int slave, int address) throws IOException, ModbusError {
        int[] values = readInputRegisters(slave, address, 1);// 6 0 1
        return values[0];
    }

    public boolean readInput(int slave, int address) throws IOException, ModbusError {
        int[] values = readInputs(slave, address, 1);
        return values[0] > 0;
    }
}
